/*
 * Copyright (c) 2009 Jostein Austvik Jacobsen
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package no.infotavle;

import java.util.ArrayList;
import java.util.List;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.awt.*;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.net.URL;

import javax.imageio.ImageIO;

import twitter4j.Status;
import twitter4j.Twitter;
import twitter4j.TwitterException;

import no.yr.*;
import no.trafikanten.*;

class Infotavle implements KeyListener, MouseListener {

	private boolean running;
	
	private Settings settings;
	
	private Date nextYrUpdate = new Date();
	private Date nextTrafikantenUpdate = new Date();
	private Date nextTwitterUpdate = new Date();
	
	private Frame mainFrame;
	
	public Forecast yr;
	private WeatherTabular yr_now;
	
	private HashMap<String, BufferedImage> images = new HashMap<String, BufferedImage>();
	private Rectangle bounds;
	
	private Font font_location;
	private Font font_weather_datebar;
	private Font font_time_of_day;
	private Font font_temperature;
	private Font font_mps_mm;
	private Font font_temperature_small;
	private Font font_mps_mm_small;
	private Font font_platform;
	private Font font_line;
	private Font font_departures;
	private Font font_twitter_headline;
	private Font font_twitter_text;
	private Font font_twitter_date;
	private Color TRAFIKANTEN_BLUE = new Color((float)0.25390625,(float)0.390625,(float)0.7421875);
	private FontMetrics fontmetrics_temperature;
	private FontMetrics fontmetrics_mps_mm;
	private FontMetrics fontmetrics_temperature_small;
	private FontMetrics fontmetrics_mps_mm_small;
	private FontMetrics fontmetrics_platform;
	private FontMetrics fontmetrics_twitter_headline;
	private FontMetrics fontmetrics_twitter_text;
	
	private Date now;
	
	public ArrayList<DepartureGroup> departures;
	public ArrayList<Integer> departure_stop;
	
	List<Status> timeline = null;
	
	GraphicsDevice device;
	
	public Infotavle(GraphicsDevice device_) {
		device = device_;
		
		settings = new Settings("settings.xml"); // Load settings
		departures = new ArrayList<DepartureGroup>();
		departure_stop = new ArrayList<Integer>();
		
		GraphicsConfiguration gc = device.getDefaultConfiguration();
		mainFrame = new Frame(gc);
		mainFrame.addKeyListener(this);
		mainFrame.addMouseListener(this);
		mainFrame.setUndecorated(true);
		mainFrame.setIgnoreRepaint(true);
		
		font_location = new Font(null, Font.PLAIN, 36);
		font_weather_datebar = new Font(null, Font.BOLD, 15);
		font_time_of_day = new Font(null, Font.PLAIN, 22);
		font_temperature = new Font(null, Font.BOLD, 32);
		font_mps_mm = new Font(null, Font.BOLD, 16);
		font_temperature_small = new Font(null, Font.PLAIN, 18);
		font_mps_mm_small = new Font(null, Font.PLAIN, 12);
		font_platform = new Font(null, Font.BOLD, 14);
		font_line = new Font(null, Font.BOLD, 19);
		font_departures = new Font(null, Font.BOLD, 15);
		font_twitter_headline = new Font(null, Font.PLAIN, 16);
		font_twitter_text = new Font(null, Font.PLAIN, 15);
		font_twitter_date = new Font(null, Font.ITALIC, 12);
		fontmetrics_temperature = mainFrame.getFontMetrics(font_temperature);
		fontmetrics_mps_mm = mainFrame.getFontMetrics(font_mps_mm);
		fontmetrics_temperature_small = mainFrame.getFontMetrics(font_temperature_small);
		fontmetrics_mps_mm_small = mainFrame.getFontMetrics(font_mps_mm_small);
		fontmetrics_platform = mainFrame.getFontMetrics(font_platform);
		fontmetrics_twitter_headline = mainFrame.getFontMetrics(font_twitter_headline);
		fontmetrics_twitter_text = mainFrame.getFontMetrics(font_twitter_text);

		// Load graphics
		File imageDir = new File("images");
		String[] imageFiles = imageDir.list();
		if (imageFiles == null) {
			System.err.println("Image folder 'images' not found");
			device.setFullScreenWindow(null);
			System.exit(0);
		} else {
			for (int i = 0; i < imageFiles.length; i++) {
				String extension = imageFiles[i].substring(imageFiles[i].length()-4);
				if (extension.equalsIgnoreCase(".png")
						|| extension.equalsIgnoreCase(".gif")
						|| extension.equalsIgnoreCase(".jpg")) {
					try {
						BufferedImage img = ImageIO.read(new File("images/"+imageFiles[i]));
						images.put(imageFiles[i], img);
					} catch (IOException e) {
						System.err.println("error loading image 'images/"
								+imageFiles[i]+"': "+e.toString());
					}
				}
			}
		}
		
		running = false;
	}
	
	public void drawLoop() {
		running = true;
		
		device.setFullScreenWindow(mainFrame);
		
		mainFrame.createBufferStrategy(2);
		BufferStrategy bufferStrategy = mainFrame.getBufferStrategy();
		
		bounds = mainFrame.getBounds();
		if (settings.offset.x > bounds.width) settings.offset.x = 0;
		if (settings.offset.width+settings.offset.x > bounds.width) settings.offset.width = bounds.width-settings.offset.x;
		if (settings.offset.y > bounds.width) settings.offset.y = 0;
		if (settings.offset.height+settings.offset.y > bounds.height) settings.offset.height = bounds.height-settings.offset.y;
		
		now = new Date();
		nextYrUpdate = new Date(now.getTime()+1000);
		nextTrafikantenUpdate = new Date(now.getTime()+1000);
		nextTwitterUpdate = new Date(now.getTime()+1000);
		
		Font font_notes = new Font(null, Font.PLAIN, 12);
		while (running) {
			for (int b = 0; b < 2; b++) {
				if (!bufferStrategy.contentsLost()) {
					now = new Date();
					Graphics g = bufferStrategy.getDrawGraphics();
					
					// Black background
					g.setColor(Color.black);
					g.fillRect(0, 0, bounds.width, bounds.height);
					
					// Location
					g.drawImage(images.get("location_bg.png"), settings.offset.x, settings.offset.y, null);
					g.setColor(Color.white);
					g.setFont(font_location);
					g.drawString(settings.name, settings.offset.x+15, settings.offset.y+55);
					
					// Network connection status
					// If the first listed stop in trafikanten haven't been updated in
					// 61 seconds, we assume that the network connection isn't working
					if (settings.stop_updated != null
							&& settings.stop_updated.size() > 0
							&& settings.stop_updated.get(0).getTime()-now.getTime() > 61000) {
						g.drawImage(images.get("Gnome-network-offline.png"), settings.offset.x+371, settings.offset.y+20, null);
					}

					// Contents
					paintTwitter(g);
					paintTrafikanten(g);
					paintYr(g);
					
					g.setFont(font_notes);
					g.setColor(Color.white);
					if (departures != null && departures.size() > 0)
						g.drawString("Sanntidsinformasjon fra trafikanten.no", settings.offset.x+330, settings.offset.y+settings.offset.height-113-24);
					if (yr != null) {
						g.drawString(yr.weatherDataInfoText, settings.offset.x+330, settings.offset.y+settings.offset.height-113-12);
						g.drawString("("+yr.weatherDataInfoUrl+")", settings.offset.x+330, settings.offset.y+settings.offset.height-113);
					}
					
					// Update screen
					bufferStrategy.show();
					g.dispose();
				}
				try {
					Thread.sleep((int) 100);
				} catch (InterruptedException e) {
				}
			}
		}

		device.setFullScreenWindow(null);
	}

	private void paintTwitter(Graphics g) {
		update:
		if (nextTwitterUpdate.before(now)) {
			try {
				Twitter twitter = new Twitter(settings.twitter_username,settings.twitter_password);
				if (twitter.rateLimitStatus().getRemainingHits() <
						(twitter.rateLimitStatus().getDateTime().getTime()-now.getTime())/60000) {
					System.out.println(twitter.rateLimitStatus().getRemainingHits()+" < ("+
							twitter.rateLimitStatus().getDateTime().getTime()+"-"+now.getTime()+")/60000");
					nextTwitterUpdate = new Date(twitter.rateLimitStatus().getDateTime().getTime()+5000);
					break update;
				}
				
				List<Status> newTimeline = twitter.getFriendsTimeline();
				if (newTimeline != null && newTimeline.size() != 0) {
					timeline = newTimeline;
					nextTwitterUpdate = new Date(now.getTime()+60000);
				}
				else
					nextTwitterUpdate = new Date(now.getTime()+10000);
			} catch (TwitterException e) {
				e.printStackTrace();
			}
		}
		
		if (timeline == null)
			return;
		
		int y = settings.offset.y+80;
		for (Status s : timeline) {
			ArrayList<String> broken = breakString(s.getText(), fontmetrics_twitter_text, settings.offset.width-501-115);
			
			// Determine frame dimensions
			int h = broken.size()*20+20;
			if (h < 90) h = 90;
			if ( y + h > settings.offset.y+settings.offset.height-150)
				break;
			
			// Draw frame
			if (now.getTime()-s.getCreatedAt().getTime() < 6*60*60*1000)
				g.setColor(new Color((float)0.9,(float)0.9,(float)0.9));
			else
				g.setColor(new Color((float)0.7,(float)0.7,(float)0.7));
			g.fillRect(settings.offset.x,y,settings.offset.width-501,h);
			g.setColor(new Color((float)(210/256.),(float)(210/256.),(float)(210/256.)));
			g.drawRect(settings.offset.x,y,settings.offset.width-501,h);
			g.drawRect(settings.offset.x+1,y+1,settings.offset.width-503,h-2);
			
			// Paint header
			g.setColor(Color.black);
			g.setFont(font_twitter_headline);
			String headline = s.getUser().getName();
			if (fontmetrics_twitter_headline.stringWidth(headline) < 100) {
				g.drawString(headline, settings.offset.x+5, y+20);
			} else {
				int l = headline.length();
				while (l > 1 && fontmetrics_twitter_headline.stringWidth(headline.substring(0,l)) >= 100)
					l--;
				g.drawString(headline.substring(0,l), settings.offset.x+5, y+20);
			}
			
			// Paint timestamp
			g.setColor(Color.darkGray);
			g.setFont(font_twitter_date);
			g.drawString(twitterTime(s.getCreatedAt(),now), settings.offset.x+5, y+h-5);
			
			// Paint text
			g.setColor(Color.black);
			g.setFont(font_twitter_text);
			for (int i = 0, y2 = 20; i < broken.size(); i++, y2 += 20) {
				g.drawString(broken.get(i), settings.offset.x+115, y+y2);
			}
			
			try {
				URL img_url = s.getUser().getProfileImageURL();
				if (!images.containsKey(img_url.getFile()+"_"+s.getUser().getName())) {
					BufferedImage img = ImageIO.read(img_url);
					images.put(img_url.getFile()+"_"+s.getUser().getName(), img);
				}
				g.drawImage(images.get(img_url.getFile()+"_"+s.getUser().getName()), settings.offset.x+20, y+25, null);
			} catch (IOException e) {}
			
			y += h+2;
		}
	}

	private String twitterTime(Date createdAt, Date now) {
		String text;
		long timeDiff = now.getTime() - createdAt.getTime();
		if (timeDiff < 60*1000) {
			text = "Under 1 minutt siden";
		} else if (timeDiff < 60*60*1000) {
			long min = timeDiff/1000/60;
			if (min == 1) text = min+" minutt siden";
			else text = min+" minutter siden";
		} else if (timeDiff < 6*60*60*1000) {
			long hours = timeDiff/1000/60/60;
			long mins = (timeDiff-hours*60*60*1000)/1000/60;
			if (hours == 1) text = "1 time og ";
			else text = hours+" timer og ";
			if (mins == 1) text += "1 minutt siden";
			else text += mins+" minutter siden";
		} else if (timeDiff < 7*24*60*60*1000) {
			Calendar cal = Calendar.getInstance();
			cal.setTime(createdAt);
			switch (cal.get(Calendar.DAY_OF_WEEK)) {
			case Calendar.MONDAY: text = "Mandag"; break;
			case Calendar.TUESDAY: text = "Tirsdag"; break;
			case Calendar.WEDNESDAY: text = "Onsdag"; break;
			case Calendar.THURSDAY: text = "Torsdag"; break;
			case Calendar.FRIDAY: text = "Fredag"; break;
			case Calendar.SATURDAY: text = "Lørdag"; break;
			case Calendar.SUNDAY: text = "Søndag"; break;
			default: text = "?";
			}
			text += " "+cal.get(Calendar.HOUR_OF_DAY);
			if (cal.get(Calendar.MINUTE) < 10)
				text += ":0"+cal.get(Calendar.MINUTE);
			else
				text += ":"+cal.get(Calendar.MINUTE);
		} else {
			long days = timeDiff/1000/60/60/24;
			if (days == 1) text = "1 dag siden";
			else text = days+" dagr siden";
		}
		return text;
	}

	private ArrayList<String> breakString(String text, FontMetrics metrics, int max_width) {
		ArrayList<String> broken = new ArrayList<String>();
		
		for (int i = 0, j; i < text.length(); i = j) {
			j = i+1;
			while (j < text.length() && metrics.stringWidth(text.substring(i,j)) < max_width)
				j++;
			if (j != text.length()) {
				for (int k = j-1; k > i; k--) {
					if (text.charAt(k) == ' ') {
						j = k;
						break;
					}
				}
			}
			broken.add(text.substring(i,j));
			if (j < text.length() && text.charAt(j) == ' ') j++;
		}
		
		return broken;
	}

	private void paintYr(Graphics g) {
		if (now.after(nextYrUpdate)) {
			WeatherTabular yr_prev0 = null;
			if (yr != null)
				yr_prev0 = yr.weatherTabular.get(0);
			try {
				yr = new Forecast(settings.yr);
				if (yr_prev0 != null
						&& !yr.weatherTabular.get(0).from.equals(yr_prev0.from))
					yr_now = yr_prev0;
				// lastYrUpdate = now;
				nextYrUpdate = yr.nextUpdate;

			} catch (Exception e) {
				// This probably just means that 'new Forecast(...)' failed, which
				// again probably just means that there are no internet access.
				// Improved testing and handling for this can be implemented later
			}
			if (nextYrUpdate.getTime()-now.getTime() > 24*60*60*1000)
				nextYrUpdate = new Date(now.getTime()+24*60*60*1000);
			if (nextYrUpdate.getTime()-now.getTime() < 60*1000)
				nextYrUpdate = new Date(now.getTime()+60*1000);

			if (yr != null && yr.weatherTabular.size() > 0 && yr_now != null
					&& now.after(yr_now.to)) {
				yr_now = yr.weatherTabular.get(0);
				yr.weatherTabular.remove(0);
			}
		}
		if (yr == null || yr.weatherTabular == null || yr.weatherTabular.size() <= 1) {
			nextYrUpdate = new Date(now.getTime()+1000);
			return;
		}
		
		// Weather
		WeatherTabular first;
		int second;
		if (yr_now == null) {
			second = 1;
			first = yr.weatherTabular.get(0);
		} else {
			second = 0;
			first = yr_now;
		}
		
		// weather now
		g.drawImage(images.get("weather_now.png"), settings.offset.x, settings.offset.y+settings.offset.height-149, null);
		g.drawImage(images.get(lookupSymbol(first.symbol, first.period)), settings.offset.x+120, settings.offset.y+settings.offset.height-125, null);
		
		g.setColor(Color.black);
		g.setFont(font_mps_mm);
		g.drawString(first.windSpeedName,
					 settings.offset.x+300-fontmetrics_mps_mm.stringWidth(first.windSpeedName), settings.offset.y+settings.offset.height-68);
		g.drawString(first.precipitation+" mm",
					 settings.offset.x+300-fontmetrics_mps_mm.stringWidth(first.precipitation+" mm"), settings.offset.y+settings.offset.height-103);
		if (first.temperature < 0)
			g.setColor(Color.blue);
		else
			g.setColor(Color.red);
		g.setFont(font_temperature);
		g.drawString((int) first.temperature+"°"+first.temperatureUnit.toUpperCase().charAt(0),
					 settings.offset.x+60-fontmetrics_temperature.stringWidth((int) first.temperature+"°"
							 			+first.temperatureUnit.toUpperCase().charAt(0)) / 2, settings.offset.y+settings.offset.height-58);
		
		g.setFont(font_weather_datebar);
		g.setColor(Color.white);
		g.drawImage(images.get("weather_datebar_now.png"), settings.offset.x, settings.offset.y+settings.offset.height-22, null);
		g.drawString(formatDate(first.from)+" "+formatTime(now), settings.offset.x+6, settings.offset.y+settings.offset.height-6);
		
		switch (first.period) {
		case NIGHT:
			g.setColor(Color.gray);
			g.setFont(font_mps_mm);
			g.drawString("kl. 00.00-06.00", settings.offset.x+15, settings.offset.y+settings.offset.height-128);
			g.setColor(Color.black);
			g.setFont(font_time_of_day);
			g.drawString("Natt", settings.offset.x+15, settings.offset.y+settings.offset.height-108);
			break;
		case MORNING:
			g.setColor(Color.gray);
			g.setFont(font_mps_mm);
			g.drawString("kl. 06.00-12.00", settings.offset.x+15, settings.offset.y+settings.offset.height-128);
			g.setColor(Color.black);
			g.setFont(font_time_of_day);
			g.drawString("Morgen", settings.offset.x+15, settings.offset.y+settings.offset.height-108);
			break;
		case DAY:
			g.setColor(Color.gray);
			g.setFont(font_mps_mm);
			g.drawString("kl. 12.00-18.00", settings.offset.x+15, settings.offset.y+settings.offset.height-128);
			g.setColor(Color.black);
			g.setFont(font_time_of_day);
			g.drawString("Dag", settings.offset.x+15, settings.offset.y+settings.offset.height-108);
			break;
		case EVENING:
			g.setColor(Color.gray);
			g.setFont(font_mps_mm);
			g.drawString("kl. 18.00-00.00", settings.offset.x+15, settings.offset.y+settings.offset.height-128);
			g.setColor(Color.black);
			g.setFont(font_time_of_day);
			g.drawString("Kveld", settings.offset.x+15, settings.offset.y+settings.offset.height-108);
			break;
		}
		
		for (int i = 0, x = settings.offset.x+321; i < yr.weatherTabular.size() && x+100 <= settings.offset.x+settings.offset.width; i++, x += 101) {
			g.drawImage(images.get("weather_later.png"), x, settings.offset.y+settings.offset.height-110, null);
			g.drawImage(images.get("weather_datebar.png"), x, settings.offset.y+settings.offset.height-22, null);
			if (yr.weatherTabular.get(second+i).period != WeatherTabular.Period.NIGHT)
				g.drawImage(images.get("weather_datebar_spacer.png"), x-1, settings.offset.y+settings.offset.height-22, null);
		}
		
		for (int i = 0, x = settings.offset.x+321; i < yr.weatherTabular.size() && x+100 <= settings.offset.x+settings.offset.width; i++, x += 101) {
			WeatherTabular t = yr.weatherTabular.get(second+i);
			
			g.setColor(Color.white);
			g.setFont(font_weather_datebar);
			switch (t.period) {
			case NIGHT:
				g.drawString("00-06", x+25, settings.offset.y+settings.offset.height-27);
				g.drawString(formatDate(t.to), x+6, settings.offset.y+settings.offset.height-6);
				break;
			case MORNING:
				g.drawString("06-12", x+25, settings.offset.y+settings.offset.height-27);
				break;
			case DAY:
				g.drawString("12-18", x+25, settings.offset.y+settings.offset.height-27);
				break;
			case EVENING:
				g.drawString("18-00", x+25, settings.offset.y+settings.offset.height-27);
				break;
			}

			g.drawImage(getScaledImage(lookupSymbol(t.symbol, t.period), 50, 50), x+5, settings.offset.y+settings.offset.height-108, null);

			g.setColor(Color.black);
			g.setFont(font_mps_mm_small);
			g.drawString(t.windSpeedName,x+95-fontmetrics_mps_mm_small.stringWidth(t.windSpeedName), settings.offset.y+settings.offset.height-47);
			g.drawString(t.precipitation+" mm", x+95-fontmetrics_mps_mm_small.stringWidth(t.precipitation+ " mm"), settings.offset.y+settings.offset.height-58);
			
			if (t.temperature < 0)
				g.setColor(Color.blue);
			else
				g.setColor(Color.red);
			g.setFont(font_temperature_small);
			g.drawString((int) t.temperature+"°", x+95-fontmetrics_temperature_small.stringWidth((int) t.temperature+"°"), settings.offset.y+settings.offset.height-78);

			g.setColor(Color.black);
		}
		g.fillRect(settings.offset.x+settings.offset.width, settings.offset.y+settings.offset.height-22, (int)bounds.getWidth(), settings.offset.y+settings.offset.height); // remove text outside designated area
	}

	private void paintTrafikanten(Graphics g) {
		if (now.after(nextTrafikantenUpdate)) {
			// for each stop
			for (int s = 0; s < settings.stop_id.size(); s++) {
				Stop stop = new Stop(settings.stop_name.get(s),
						settings.stop_id.get(s));
				if (stop == null)
					continue;
				ArrayList<DepartureGroup> depGroups;
				try {
					depGroups = stop.getDepartures();
				} catch (Exception e) {
					System.err.println("Could not get departures for "
							+ settings.stop_name.get(s)+"(id:"
							+ settings.stop_id.get(s)+"): "+e.toString());
					e.printStackTrace();
					continue;
				}
				
				if (depGroups == null)
					continue;
				
				settings.stop_updated.set(s, now);

				// for each platform
				for (int p = 0; p < settings.stop_platform_nr.get(s).size(); p++) {

					// for each line
					for (int dg = 0; dg < depGroups.size(); dg++) {
						DepartureGroup depGroup = depGroups.get(dg);

						if (depGroup.getDirection() != settings.stop_platform_nr.get(s).get(p))
							continue;

						// Check whether we are interested in displaying departures of
						// this line from this platform at this stop
						boolean includeByDefault = true;
						if (settings.stop_platform_include.get(s).get(p).size() > 0)
							includeByDefault = false;
						boolean include = includeByDefault;
						if (includeByDefault) {
							for (int d = 0; d < settings.stop_platform_exclude.get(s).get(p).size(); d++) {
								if (settings.stop_platform_exclude.get(s).get(p).get(d) == depGroup.getLineNumber()) {
									include = false;
									break;
								}
							}
						} else {
							for (int d = 0; d < settings.stop_platform_include.get(s).get(p).size(); d++) {
								if (settings.stop_platform_include.get(s).get(p).get(d) == depGroup.getLineNumber()) {
									include = true;
									break;
								}
							}
						}

						if (include) {
							for (int i = 0; i < departures.size(); i++) {

								if (depGroup.getLineNumber() == departures.get(
										i).getLineNumber()
										&& depGroup.getDirection() == departures.get(i).getDirection()
										&& settings.stop_id.get(s) == departure_stop.get(i)) {

									// Update departures
									departures.set(i, depGroup);

									include = false;
								}

							}
							if (include) {
								// Add departures
								departures.add(depGroup);
								departure_stop.add(settings.stop_id.get(s));
							}
						}
					}
				}
			}
			nextTrafikantenUpdate = new Date(now.getTime()+30*1000);
		}

		int y = settings.offset.y;
		for (int s = 0; s < settings.stop_id.size(); s++) {
			for (int p = 0; p < settings.stop_platform_nr.get(s).size(); p++) {
				boolean hasPaintedPlatformHeader = false;
				for (int dg = 0; dg < departures.size(); dg++) {
					if (departure_stop.get(dg) == settings.stop_id.get(s)
							&& departures.get(dg).getDirection() == settings.stop_platform_nr.get(s).get(p)) {
						if (!hasPaintedPlatformHeader) {
							g.drawImage(images.get("platform.png"), settings.offset.x+settings.offset.width-499, y, null);
							g.setFont(font_platform);
							g.setColor(Color.black);
							String text = settings.stop_name.get(s)+" - "+settings.stop_platform_name.get(s).get(p);
							g.drawString(text, settings.offset.x+settings.offset.width-499+250 - fontmetrics_platform.stringWidth(text)/2, y+23);
							try {
								y += images.get("platform.png").getHeight();
							} catch (NullPointerException e) {
								System.out.println("images: "+images);
								System.out.println("images.get(\"platform.png\"): "+images.get("platform.png"));
								System.out.println("images.get(\"platform.png\").getHeight(): "+images.get("platform.png").getHeight());
								throw e;
							}
							hasPaintedPlatformHeader = true;
						}
						
						if (departures.get(dg).getDepartureString(now, false).length() == 0)
							continue;
						
						g.drawImage(images.get("line.png"), settings.offset.x+settings.offset.width-499, y, null);
						g.setColor(Color.white);
						g.setFont(font_line);
						String text = ""+departures.get(dg).getLineNumber();
						switch (text.length()) {
						case 1:
							g.fillRect(settings.offset.x+settings.offset.width-499+7, y+14, 25, 25);
							g.setColor(TRAFIKANTEN_BLUE);
							g.drawString(text, settings.offset.x+settings.offset.width-499+14, y+34);
							text = departures.get(dg).getName();
							g.setColor(Color.white);
							g.drawString(text, settings.offset.x+settings.offset.width-499+38, y+35);
							break;
						case 2:
							g.fillRect(settings.offset.x+settings.offset.width-499+7, y+14, 25, 25);
							g.setColor(TRAFIKANTEN_BLUE);
							g.drawString(text, settings.offset.x+settings.offset.width-499+7, y+34);
							text = departures.get(dg).getName();
							g.setColor(Color.white);
							g.drawString(text, settings.offset.x+settings.offset.width-499+38, y+35);
							break;
						case 3:
							g.fillRect(settings.offset.x+settings.offset.width-499+7, y+14, 40, 25);
							g.setColor(TRAFIKANTEN_BLUE);
							g.drawString(text, settings.offset.x+settings.offset.width-499+7, y+34);
							text = departures.get(dg).getName();
							g.setColor(Color.white);
							g.drawString(text, settings.offset.x+settings.offset.width-499+38+15, y+35);
							break;
						}
						switch (departures.get(dg).getTransportationId()) {
						case 0:
							g.drawImage(images.get("tram_icon.png"),settings.offset.x+settings.offset.width-39,y+46-39,null);
							break;
						case 1:
							g.drawImage(images.get("bus_icon.png"),settings.offset.x+settings.offset.width-39,y+46-39,null);
							break;
						case 2:
							g.drawImage(images.get("subway_icon.png"),settings.offset.x+settings.offset.width-39,y+46-39,null);
							break;
						case 3:
							g.drawImage(images.get("boat_icon.png"),settings.offset.x+settings.offset.width-39,y+46-39,null);
							break;
						}
						y += images.get("line.png").getHeight();

						g.drawImage(images.get("departures.png"), settings.offset.x+settings.offset.width-499, y, null);
						g.setColor(Color.yellow);
						g.setFont(font_departures);
						if (now.getTime()-settings.stop_updated.get(s).getTime() > 61000)
							text = departures.get(dg).getDepartureString(now,true);
						else
							text = departures.get(dg).getDepartureString(now,false);
						g.drawString(text, settings.offset.x+settings.offset.width-499+15, y+16);
						g.setColor(Color.black);
						g.fillRect(settings.offset.x+settings.offset.width, y, bounds.width-(settings.offset.x+settings.offset.width), 24); // maintain black border
						y += images.get("departures.png").getHeight();
					}
				}
			}
		}
		
		g.setColor(Color.black);
		if (y > settings.offset.y+settings.offset.height-150) {
			g.fillRect(settings.offset.x+settings.offset.width-499,
					   settings.offset.y+settings.offset.height-150,
					   499,
					   bounds.height-(settings.offset.y+settings.offset.height-150));
		}
	}

	public String formatTime(Date date) {
		Calendar cal = Calendar.getInstance();
		cal.setTime(date);
		String text = cal.get(Calendar.HOUR_OF_DAY)+":";
		int minute = cal.get(Calendar.MINUTE);
		int second = cal.get(Calendar.SECOND);
		if (minute < 10)
			text += "0";
		text += minute+":";
		if (second < 10)
			return text+"0"+second;
		else
			return text+second;
	}

	public String formatDate(Date date) {
		Calendar cal = Calendar.getInstance();
		cal.setTime(date);
		String text = "";
		switch (cal.get(Calendar.DAY_OF_WEEK)) {
		case Calendar.MONDAY:
			text = "Mandag";
			break;
		case Calendar.TUESDAY:
			text = "Tirsdag";
			break;
		case Calendar.WEDNESDAY:
			text = "Onsdag";
			break;
		case Calendar.THURSDAY:
			text = "Torsdag";
			break;
		case Calendar.FRIDAY:
			text = "Fredag";
			break;
		case Calendar.SATURDAY:
			text = "Lørdag";
			break;
		case Calendar.SUNDAY:
			text = "Søndag";
			break;
		}
		text += ", "+cal.get(Calendar.DAY_OF_MONTH)+". ";
		switch (cal.get(Calendar.MONTH)) {
		case Calendar.JANUARY:
			text += "Januar";
			break;
		case Calendar.FEBRUARY:
			text += "Februar";
			break;
		case Calendar.MARCH:
			text += "Mars";
			break;
		case Calendar.APRIL:
			text += "April";
			break;
		case Calendar.MAY:
			text += "Mai";
			break;
		case Calendar.JUNE:
			text += "Juni";
			break;
		case Calendar.JULY:
			text += "Juli";
			break;
		case Calendar.AUGUST:
			text += "August";
			break;
		case Calendar.SEPTEMBER:
			text += "September";
			break;
		case Calendar.OCTOBER:
			text += "Oktober";
			break;
		case Calendar.NOVEMBER:
			text += "November";
			break;
		case Calendar.DECEMBER:
			text += "Desember";
			break;
		}
		return text;
	}

	public BufferedImage getScaledImage(String img, int width, int height) {
		// Add the resized instance to the image map so we don't have to create
		// it again
		if (!images.containsKey(img+"_"+width+"x"+height)) {
			BufferedImage scaledImage = new BufferedImage(width, height,
					BufferedImage.TYPE_INT_ARGB);
			Graphics2D graphics2D = scaledImage.createGraphics();
			graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
					RenderingHints.VALUE_INTERPOLATION_BILINEAR);
			graphics2D.drawImage(images.get(img), 0, 0, width, height, null);
			graphics2D.dispose();

			images.put(img+"_"+width+"x"+height, scaledImage);
		}

		return images.get(img+"_"+width+"x"+height);
	}

	public String lookupSymbol(WeatherTabular.Symbol s, WeatherTabular.Period p) {
		String day_or_night = "d";
		if (p == WeatherTabular.Period.EVENING
				|| p == WeatherTabular.Period.NIGHT) {
			day_or_night = "n";
		}
		switch (s) {
		case SUN:
			return "01"+day_or_night+".png";
		case FAIR:
			return "02"+day_or_night+".png";
		case PARTLY_CLOUDY:
			return "03"+day_or_night+".png";
		case CLOUDY:
			return "04.png";
		case RAIN_SHOWERS:
			return "05"+day_or_night+".png";
		case RAIN_SHOWERS_WITH_THUNDER:
			return "06"+day_or_night+".png";
		case SLEET_SHOWERS:
			return "07"+day_or_night+".png";
		case SNOW_SHOWERS:
			return "08"+day_or_night+".png";
		case RAIN:
			return "09.png";
		case HEAVY_RAIN:
			return "10.png";
		case RAIN_AND_THUNDER:
			return "11.png";
		case SLEET:
			return "12.png";
		case SNOW:
			return "13.png";
		case SNOW_AND_THUNDER:
			return "14.png";
		case FOG:
			return "15.png";
		case SUN_POLAR_NIGHT:
			return "16.png";
		case PARTLY_CLOUDY_POLAR_NIGHT:
			return "17.png";
		case RAIN_SHOWERS_POLAR_NIGHT:
			return "18.png";
		case SNOW_SHOWERS_POLAR_NIGHT:
			return "19.png";
		default:
			return null;
		}
	}

	public static void main(String[] args) {
		try {
			GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
			GraphicsDevice device = env.getDefaultScreenDevice();
			Infotavle infotavle = new Infotavle(device);
			infotavle.drawLoop();
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.exit(0);
	}

	public void keyPressed(KeyEvent e) {
	}

	public void keyReleased(KeyEvent e) {
	}

	public void keyTyped(KeyEvent e) {
		this.running = false;
	}

	public void mouseClicked(MouseEvent e) {
		this.running = false;
	}

	public void mouseEntered(MouseEvent e) {
	}

	public void mouseExited(MouseEvent e) {
	}

	public void mousePressed(MouseEvent arg0) {
	}

	public void mouseReleased(MouseEvent arg0) {
	}

	public boolean isRunning() {
		return this.running;
	}
}
